using System;
using System.Collections.Generic;
using System.Security.Permissions;
using System.Security.Cryptography;
using System.Security.Cryptography.Pkcs;
using System.Security.Cryptography.X509Certificates;
using System.Text;

namespace TBOCrypto
{
    public class Envelope
    {
        private const string _signerName = "aKs";
        private bool _isVerified = false;
        private static byte[] _messageSignedAndEncoded;

        public Envelope()
        {
        }
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="message">Message to process</param>
        public Envelope(string message)
        {
            Encoding unicode = Encoding.Unicode;
            byte[] msgBytes = unicode.GetBytes(message);

            X509Certificate2 signerCert = GetSignerCert();
            X509Certificate2 recipientCert = GetRecipientCert();

            SignMsg(msgBytes, signerCert, recipientCert);
        }

        /// <summary>
        /// Gets signer's certificate from store
        /// </summary>
        /// <returns></returns>
        public X509Certificate2 GetSignerCert()
        {
            X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
            store.Open(OpenFlags.ReadOnly);

            if (store.Certificates.Count == 0)
                throw new Exception("No certificates in store");

            X509Certificate2 cert = store.Certificates[0];//.Find(X509FindType.FindBySubjectName, _signerName, false)[0];

            store.Close();

            return cert;
        }
        /// <summary>
        /// Gets recipient certificate
        /// </summary>
        /// <returns>Recipient certificate</returns>
        public X509Certificate2 GetRecipientCert()
        {
            X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
            store.Open(OpenFlags.MaxAllowed);

            if (store.Certificates.Count == 0)
                throw new Exception("No certificates in store");

            X509Certificate2 cert = store.Certificates.Find(X509FindType.FindBySubjectName, "Recipient", false)[0];

            store.Close();

            return cert;
        }

        /// <summary>
        /// Signs the message with the private key of the signer.
        /// </summary>
        /// <param name="msg">Message</param>
        /// <param name="signerCert">Signer's certificate</param>
        /// <returns>Encoded message</returns>
        protected byte[] SignMsg(Byte[] msg, X509Certificate2 signerCert, X509Certificate2 recipientCert)
        {
            ContentInfo contentInfo = new ContentInfo(msg);
            SignedCms signedCms = new SignedCms(contentInfo);
            CmsSigner cmsSigner = new CmsSigner(signerCert);
            signedCms.ComputeSignature(cmsSigner);

            byte[] signedMsg = signedCms.Encode();
            VerifyMsg(signedMsg);

            contentInfo = new ContentInfo(signedMsg);
            EnvelopedCms envelopeCms = new EnvelopedCms(contentInfo);
            CmsRecipient cmsRecipient = new CmsRecipient(recipientCert);
            envelopeCms.Encrypt(cmsRecipient);
            _messageSignedAndEncoded = envelopeCms.Encode();

            return _messageSignedAndEncoded;
        }
        /// <summary>
        /// Verifies the encoded SignedCms message
        /// </summary>
        /// <param name="encodedSignedCms">Encoded message</param>
        /// <returns>Value indicating if verify succeded</returns>
        protected bool VerifyMsg(byte[] encodedSignedCms)
        {
            SignedCms signedCms = new SignedCms();

            signedCms.Decode(encodedSignedCms);

            try
            {
                signedCms.CheckSignature(true);
            }
            catch
            {
                _isVerified = false;
                return false;
            }

            _isVerified = true;
            return true;
        }

        /// <summary>
        /// Gets signed and encoded message in byte[] format
        /// </summary>
        public byte[] MessageSignedAndEncoded
        {
            get { return _messageSignedAndEncoded; }
        }
        /// <summary>
        /// Gets signed and encoded message in string format
        /// </summary>
        public string MessageSignedAndEncodedToString
        {
            get
            {
                Encoding en = Encoding.Unicode;

                return en.GetString(MessageSignedAndEncoded);
            }
        }
        /// <summary>
        /// Gets signed and decoded message in byte[] format
        /// </summary>
        public byte[] MessageSignedAndDecoded
        {
            get
            {
                EnvelopedCms envelopeCms = new EnvelopedCms();
                envelopeCms.Decode(_messageSignedAndEncoded);
                envelopeCms.Decrypt(envelopeCms.RecipientInfos[0]);

                SignedCms signedCms = new SignedCms();
                signedCms.Decode(envelopeCms.Encode());
                signedCms.CheckSignature(true);

                return signedCms.ContentInfo.Content;
            }
        }
        /// <summary>
        /// Gets signed and decoded message in string format
        /// </summary>
        public string MessageSignedAndDecodedToString
        {
            get
            {
                Encoding en = Encoding.Unicode;

                return en.GetString(MessageSignedAndDecoded);
            }
        }
        /// <summary>
        /// Gets value indicating if message is verified
        /// </summary>
        public bool IsVerified
        {
            get { return _isVerified; }
        }
    }
}
